package horncomb.bpm.process.def;

import horncomb.bpm.BpmContextHolder;
import horncomb.bpm.process.core.EnterableState;
import horncomb.bpm.process.core.ProcessDefinition;
import horncomb.bpm.process.core.ProcessInstance;
import horncomb.bpm.process.core.ProcessInstanceCreator;
import horncomb.bpm.process.core.StateDefinition;
import horncomb.bpm.process.core.Token;
import horncomb.bpm.process.core.Transition;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.util.Assert;

/**
 * @author Daniel
 */
public class DefaultProcessDefinition implements Serializable,
		ProcessDefinition {
	private static final long serialVersionUID = -6986558973181506610L;
	private String id;
	private String name;
	private String startStateId;
	private Map<String, StateDefinition> stateDefinitions = new HashMap<String, StateDefinition>();
	private Map<String, Transition> transitions = new HashMap<String, Transition>();
	private ProcessInstanceCreator processInstanceCreator;

	public DefaultProcessDefinition(final String id) {
		Assert.notNull(id, "The 'id' argument must not be null.");
		this.id = id;
	}

	/**
	 * {@inheritDoc}
	 */
	public ProcessInstance newInstance(Token parentToken, Object contextModel) {
		return BpmContextHolder.getContext().getProcessManagementService()
				.invokeCreateProcessInstance(parentToken, this, contextModel,
						this.processInstanceCreator);
	}

	/**
	 * {@inheritDoc}
	 */
	public void addState(StateDefinition stateDef) {
		Assert.notNull(stateDef);
		if (this.stateDefinitions.containsKey(stateDef.getId())) {
			// TODO throw DuplicatedException
		}
		stateDef.setProcessDefinition(this);
		this.stateDefinitions.put(stateDef.getId(), stateDef);
	}

	/**
	 * {@inheritDoc}
	 */
	public StateDefinition findStateDefinition(String stateDefinitionId) {
		Assert.hasText(stateDefinitionId);
		StateDefinition stateDef = this.stateDefinitions.get(stateDefinitionId);
		if (stateDef == null) {
			// TODO throw StateDefinitionNotFoundException;
		}
		return stateDef;
	}

	public boolean containsState(String stateId) {
		Assert.hasText(stateId);
		return this.stateDefinitions.containsKey(stateId);
	}

	/**
	 * {@inheritDoc}
	 */
	public void addTransition(Transition transition) {
		Assert.notNull(transition);
		transition.setProcessDefinition(this);
		if (!this.stateDefinitions.containsKey(transition.getSourceState()
				.getId())) {
			transition.setProcessDefinition(null);
			// TODO throw StateDefinitionNotFoundException
		}
		if (!this.stateDefinitions.containsKey(transition.getTargetState()
				.getId())) {
			transition.setProcessDefinition(null);
			// TODO throw StateDefinitionNotFoundException
		}
		this.transitions.put(transition.getId(), transition);
	}

	/**
	 * {@inheritDoc}
	 */
	public Transition findTransition(String transitionId) {
		Assert.hasText(transitionId);
		Transition trans = this.transitions.get(transitionId);
		if (trans == null) {
			// TODO throw ElementNotFoundException;
		}
		return trans;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean hasLeavingTransitions(String stateId) {
		Assert.hasText(stateId);
		for (Transition t : this.transitions.values()) {
			if (t.getSourceState().getId().equals(stateId)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public List<Transition> findLeavingTransitions(String stateId) {
		Assert.hasText(stateId);
		List<Transition> result = new ArrayList<Transition>();
		for (Transition t : this.transitions.values()) {
			if (t.getSourceState().getId().equals(stateId)) {
				result.add(t);
			}
		}
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	public EnterableState getStartState() {
		return (EnterableState) this.findStateDefinition(this.startStateId);
	}

	/**
	 * @param defaultStateDefinitions
	 *            the defaultStateDefinitions to set
	 */
	public void setStateDefinitions(
			Map<String, StateDefinition> defaultStateDefinitions) {
		this.stateDefinitions = defaultStateDefinitions;
	}

	/**
	 * @return the startStateId
	 */
	public void setStartState(EnterableState stateDef) {
		// TODO overwrite exsit startStateId
		this.startStateId = stateDef.getId();
		if (!this.containsState(stateDef.getId())) {
			this.addState(stateDef);
		}
	}

	/**
	 * @return the id
	 */
	public String getId() {
		return id;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(String id) {
		this.id = id;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DefaultProcessDefinition other = (DefaultProcessDefinition) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	/**
	 * @param processInstanceCreator
	 *            the processInstanceCreator to set
	 */
	public void setInstanceCreator(ProcessInstanceCreator processInstanceCreator) {
		Assert.notNull(processInstanceCreator);
		this.processInstanceCreator = processInstanceCreator;
	}
}
